using System;
using System.Collections.Generic;
using System.Text;

using Wrath.Xml;

namespace Wrath.CodeBox
{
    public class Language
    {
        private string name;
        private bool isLoaded;
        private bool isCaseSensitive;

        private Dictionary<string, Production> productions = new Dictionary<string,Production>();
        private Dictionary<string, TokenClass> tokenClasses = new Dictionary<string,TokenClass>();

        private Dictionary<string, string> keywords = new Dictionary<string,string>();
        private Dictionary<string, string> indents = new Dictionary<string,string>();
        private Dictionary<string, string> outdents = new Dictionary<string,string>();

        public bool IsCaseSensitive
        {
            get { return isCaseSensitive; }
        }
    
        public Dictionary<string, string> Indents
        {
            get { return indents; }
        }

        public Dictionary<string, string> Keywords
        {
            get { return keywords; }
        }

        public bool IsLoaded
        {
            get { return isLoaded; }
        }

        public string Name
        {
            get { return name; }
        }

        public TokenClass Operators
        {
            get
            {

                if (tokenClasses.ContainsKey("operator"))
                {
                    return tokenClasses["operator"];
                }
                
                return null;
            }
        }

        public Dictionary<string, string> Outdents
        {
            get { return outdents; }
        }
    
        public Dictionary<string, Production> Productions
        {
            get {  return productions; }
        }

        public Production Start
        {
            get
            {
                if (productions.ContainsKey("$start$"))
                {
                    return productions["$start$"];
                }

                return null;
            }
        }

        public Dictionary<string, TokenClass> TokenClasses
        {
            get { return tokenClasses; }
        }

        public string Xml
        {
            get 
            {
                string Value = "";
                string keys = "";

                if (!IsLoaded)
                {
                    return "";
                }

                // Build the XML from the language specification
                Value = "<language name='" + Element.XmlEncode(Name, false) + "' caseSensitive='" + IsCaseSensitive.ToString() + "'>\r\n";

                // keywords
                Value += " <keywords>";
                foreach (KeyValuePair<string, string> kvp in keywords)
                {
                    if (keys == "")
                    {
                        keys += kvp.Key;
                    }
                    else
                    {
                        keys += "|" + kvp.Key;
                    }
                }

                Value += keys + "</keywords>\r\n";
      
                // indents
                keys = "";
                Value += " <indents>";

                foreach (KeyValuePair<string, string> kvp in indents)
                {
                    if (keys == "")
                    {
                        keys += kvp.Key;
                    }
                    else
                    {
                        keys += "|" + kvp.Key;
                    }
                }

                Value += keys + "</indents>\r\n";

                // outdents
                keys = "";
                Value += " <outdents>";
                
                foreach (KeyValuePair<string, string> kvp in outdents)
                {
                    if (keys == "")
                    {
                        keys += kvp.Key;
                    }
                    else
                    {
                        keys += "|" + kvp.Key;
                    }
                }
      
                Value += keys + "</outdents>\r\n";

                // token classes      
                Value += " <tokenClasses>\r\n";
                foreach (KeyValuePair<string, TokenClass> kvp in tokenClasses)
                {
                    Value += kvp.Value.Xml + "\r\n";
                }

                Value += " </tokenClasses>\r\n";
      
                // productions
                Value += " <productions>\r\n";
                foreach (KeyValuePair<string, Production> kvp in productions)
                {
                    Value += kvp.Value.Xml + "\r\n";
                }

                Value += " </productions>\r\n";
      
                // finished
                return Value + "</language>";
            }
            set
            {
                Clear();

                try
                {
                    FileParser parser = new FileParser(value);

                    Element language = parser.FindFirstElement("language");
                    name = language.FindAttribute("name").Value;
                    isCaseSensitive = Convert.ToBoolean(language.FindAttribute("caseSensitive").Value);

                    // keywords
                    Element keysElement = language.FindFirstElement("keywords", false);
                    string[] keys = keysElement.Value.Split('|');

                    foreach (string key in keys)
                    {
                        if (IsCaseSensitive)
                            keywords.Add(key, key);
                        else
                            keywords.Add(key.ToLower(), key);
                    }

                    // indents
                    keysElement = language.FindFirstElement("indents", false);
                    keys = keysElement.Value.Split('|');

                    foreach (string key in keys)
                    {
                        if (IsCaseSensitive)
                            indents.Add(key, key);
                        else
                            indents.Add(key.ToLower(), key);
                    }

                    // outdents
                    keysElement = language.FindFirstElement("outdents", false);
                    keys = keysElement.Value.Split('|');

                    foreach (string key in keys)
                    {
                        if (IsCaseSensitive)
                            outdents.Add(key, key);
                        else
                            outdents.Add(key.ToLower(), key);
                    }

                    // token classes
                    List<Element> tokens = language.FindAll("tokenClass", true);
                    foreach (Element token in tokens)
                    {
                        TokenClass tc = new TokenClass(token.ToString(), IsCaseSensitive);
                        tokenClasses.Add(tc.Name, tc);
                    }

                    // productions
                    List<Element> prods = language.FindAll("production", true);
                    foreach (Element prod in prods)
                    {
                        Production prd = new Production(prod.ToString());
                        productions.Add(prd.Name, prd);
                    }


                    // check the language consistency and mark as ready
                    isLoaded = Check();
                }
                catch (Exception ex)
                {
                    Clear();
                    throw new Exception("Invalid language definition", ex);
                }
            }
        }

        public Language(string sourceXml)
        {
            Xml = sourceXml;
        }

        public Language()
        {
            Clear();
        }

        private void Clear()
        {
            name = "(None)";
            isLoaded = false;
            isCaseSensitive = false;
            keywords.Clear();
            indents.Clear();
            outdents.Clear();
            tokenClasses.Clear();
            productions.Clear();
        }

        private bool Check()
        {
            // ignore if there are no productions
            if (productions.Count == 0) return true;

            // check we have a starting production
            if (Start == null)
            {
                throw new Exception("The language definition does not contain a starting production");
            }

            // check that the productions can be fully satisifed by the keywords, operators, token classes and other productions
            foreach (KeyValuePair<string, Production> kvp in productions)
            {
                if (!CheckPat(kvp.Value.Pattern))
                {
                    return false;
                }
            }

            return true;
        }

        private bool CheckPat(PatternList pat)
        {
        
            //' search through each item in the pattern
            foreach (PatternItem itm in pat)
            {
                switch (itm.Type)
                {
                    case PatternItem.ItemType.Keyword:
                        if (keywords.ContainsKey(itm.Value)) return true;
                        throw new Exception("Missing keyword: " + itm.Value);

                    case PatternItem.ItemType.Operator:
                        if (Operators == null)
                        {
                            throw new Exception("Missing operator: " + itm.Value);
                        }

                        if (Operators.Match.Matches(itm.Value).Count > 0) return true;

                        throw new Exception("Missing operator: " + itm.Value);

                    case PatternItem.ItemType.Production:
                        if (productions.ContainsKey(itm.Value)) return true;

                        throw new Exception("Missing production: " + itm.Value);

                    case PatternItem.ItemType.Token:
                        if (tokenClasses.ContainsKey(itm.Value)) return true;

                        throw new Exception("Missing token class: " + itm.Value);

                    case PatternItem.ItemType.Option:
                        return CheckPat(itm.Pattern);

                    case PatternItem.ItemType.Repeat:
                        return CheckPat(itm.Pattern);

                    case PatternItem.ItemType.Alternation:
                        foreach (PatternList subitm in itm.Alternates)
                        {
                            if (!CheckPat(subitm)) return false;
                        }
            
                        return true;
                }

                return false;
            }

            return false;
        }

    }
}
